﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.XPath;
using Urb.Weather.Core.Util;
using Urb.Weather.Core;
using Urb.Weather.Core.MeasureUnit;

namespace Urb.Weather.Wunderground
{
    public class WundergroundResponseAnalyzer
    {
        public WundergroundWeatherData Analyze(string response)
        {
            XPathNavigator navigator = XmlResponseAnalyzerHelper.ValidateResponseParametersAndCreateNavigator(response);

            WundergroundWeatherData result = new WundergroundWeatherData();

            navigator.ReadSingleNode(
                "/current_observation/display_location/full", (v) => result.LocationFull = v);
            navigator.ReadSingleNode(
                "/current_observation/observation_location/city", (v) => result.City = v);
            navigator.ReadSingleNode(
                "/current_observation/observation_location/state", (v) => result.State = v);
            navigator.ReadSingleNode(
                "/current_observation/observation_location/country", (v) => result.Country = v);
            navigator.ReadSingleNode(
                "/current_observation/observation_location/country_iso3166", (v) => result.CountryISO3166 = v);
            navigator.ReadSingleNode(
                "/current_observation/observation_location/latitude", (v) => result.Latitude = v.ConvertToDecimal());
            navigator.ReadSingleNode(
                "/current_observation/observation_location/longitude", (v) => result.Longitude = v.ConvertToDecimal());
            navigator.ReadSingleNode(
                "/current_observation/observation_location/elevation", (v) => result.Elevation = v);
            navigator.ReadSingleNode(
                "/current_observation/station_id", (v) => result.StationId = v);

            navigator.ReadSingleNode(
                "/current_observation/observation_time", (v) => result.ObservationTime = v);
            navigator.ReadSingleNode(
                "/current_observation/observation_time_rfc822", (v) => result.ObservationTimeRFC822 = v.ConvertToDateTimeOffset());
            navigator.ReadSingleNode(
                "/current_observation/observation_epoch", (v) => result.ObservationEpoch = v.ConvertToInt());

            navigator.ReadSingleNode(
                "/current_observation/local_time", (v) => result.LocalTime = v);
            navigator.ReadSingleNode(
                "/current_observation/local_time_rfc822", (v) => result.LocalTimeRFC822 = v);
            navigator.ReadSingleNode(
                "/current_observation/local_epoch", (v) => result.LocalEpoch = v.ConvertToInt());
            
            navigator.ReadSingleNode(
                "/current_observation/weather", (v) => result.Weather = v);
            navigator.ReadSingleNode(
                "/current_observation/temp_f", (v) => result.TemperatureF = v.ConvertToShort());
            navigator.ReadSingleNode(
                "/current_observation/temp_c", (v) => result.TemperatureC = v.ConvertToShort());
            navigator.ReadSingleNode(
                "/current_observation/relative_humidity", (v) => result.RelativeHumidity = v);
            navigator.ReadSingleNode(
                "/current_observation/wind_dir", (v) => result.WindDir = v);
            navigator.ReadSingleNode(
                "/current_observation/wind_degrees", (v) => result.WindDegrees = v.ConvertToShort());
            navigator.ReadSingleNode(
                "/current_observation/wind_mph", (v) => result.WindMph = v.ConvertToShort());
            navigator.ReadSingleNode(
                "/current_observation/wind_gust_mph", (v) => result.WindGustMph = v);
            navigator.ReadSingleNode(
                "/current_observation/pressure_mb", (v) => result.PressureMb = v.ConvertToDecimal());
            navigator.ReadSingleNode(
                "/current_observation/pressure_in", (v) => result.PressureIn = v.ConvertToDecimal());
            navigator.ReadSingleNode(
                "/current_observation/dewpoint_f", (v) => result.DewPointF = v.ConvertToShort());
            navigator.ReadSingleNode(
                "/current_observation/dewpoint_c", (v) => result.DewPointC = v.ConvertToShort());
            navigator.ReadSingleNode(
                "/current_observation/visibility_mi", (v) => result.VisibilityMi = v.ConvertToDecimal());
            navigator.ReadSingleNode(
                "/current_observation/visibility_km", (v) => result.VisibilityKm = v.ConvertToDecimal());
            
            result.SourceXml = response;

            return result;
        }

        public WundergroundWeatherForecastsData AnalyzeForecast(string response)
        {
            XPathNavigator navigator = XmlResponseAnalyzerHelper.ValidateResponseParametersAndCreateNavigator(response);

            WundergroundWeatherForecastsData result = new WundergroundWeatherForecastsData();

            navigator.ReadSingleNode(
                "/forecast/txt_forecast/date", (v) => result.Date = v);
            
            AnalyzeForecast(navigator, result);

            result.SourceXml = response;

            return result;
        }

        public WeatherData Convert(WundergroundWeatherData wundergroundWeatherData)
        {
            if (wundergroundWeatherData == null)
                throw new ArgumentNullException("wundergroundWeatherData");

            var data = new WeatherData();

            data.MeasurementTime = wundergroundWeatherData.ObservationTimeRFC822;
            
            if (wundergroundWeatherData.TemperatureF.HasValue && wundergroundWeatherData.TemperatureC.HasValue)
            {
                data.Temperature = (((decimal)wundergroundWeatherData.TemperatureF) + TemperatureUnit.Fahrenheit.ConvertTemperatureTo(wundergroundWeatherData.TemperatureC.Value, TemperatureUnit.Celsius))/2m;
                data.TemperatureUnit = TemperatureUnit.Fahrenheit;
            }
            else if (wundergroundWeatherData.TemperatureF.HasValue)
            {
                data.Temperature = wundergroundWeatherData.TemperatureF;
                data.TemperatureUnit = TemperatureUnit.Fahrenheit;
            }
            else if (wundergroundWeatherData.TemperatureC.HasValue)
            {
                data.Temperature = wundergroundWeatherData.TemperatureC;
                data.TemperatureUnit = TemperatureUnit.Celsius;
            }
            
            if (wundergroundWeatherData.PressureIn.HasValue && wundergroundWeatherData.PressureMb.HasValue)
            {
                data.Pressure = (((decimal)wundergroundWeatherData.PressureMb) + PressureUnit.Milibar.ConvertPressureTo(wundergroundWeatherData.PressureIn.Value, PressureUnit.InchHg)) / 2m;
                data.PressureUnit = PressureUnit.Milibar;
            }
            else if (wundergroundWeatherData.PressureMb.HasValue)
            {
                data.Pressure = wundergroundWeatherData.PressureMb;
                data.PressureUnit = PressureUnit.Milibar;
            }
            else if (wundergroundWeatherData.PressureIn.HasValue)
            {
                data.Pressure = wundergroundWeatherData.PressureIn;
                data.PressureUnit = PressureUnit.InchHg;
            }

            // Atmosphere conditions
            // Forecasts

            return data;
        }

        private void AnalyzeForecast(XPathNavigator navigator, WundergroundWeatherForecastsData result)
        {
            XPathNodeIterator iterator = navigator.Select("/forecast/simpleforecast/forecastday");

            List<WundergroundWeatherForecastData> forecastDatas = new List<WundergroundWeatherForecastData>();

            if (iterator != null)
            {
                foreach (XPathNavigator nodeNavigator in iterator)
                {
                    WundergroundWeatherForecastData forecastData = new WundergroundWeatherForecastData();

                    nodeNavigator.ReadSingleNode("period", (v) => forecastData.Period = v.ConvertToByte());
                    nodeNavigator.ReadSingleNode("date/epoch", (v) => forecastData.Epoch = v.ConvertToInt());
                    nodeNavigator.ReadSingleNode("date/pretty", (v) => forecastData.TimePretty = v);
                    nodeNavigator.ReadSingleNode("date/day", (v) => forecastData.Day = v.ConvertToByte());
                    nodeNavigator.ReadSingleNode("date/month", (v) => forecastData.Month = v.ConvertToByte());
                    nodeNavigator.ReadSingleNode("date/year", (v) => forecastData.Year = v.ConvertToShort());
                    nodeNavigator.ReadSingleNode("date/hour", (v) => forecastData.Hour = v.ConvertToByte());
                    nodeNavigator.ReadSingleNode("date/min", (v) => forecastData.Min = v.ConvertToByte());
                    nodeNavigator.ReadSingleNode("date/sec", (v) => forecastData.Sec = v.ConvertToByte());
                    nodeNavigator.ReadSingleNode("date/isdst", (v) => forecastData.IsDST = v.ConvertToByte());

                    nodeNavigator.ReadSingleNode("high/fahrenheit", (v) => forecastData.HighF = v.ConvertToShort());
                    nodeNavigator.ReadSingleNode("high/celsius", (v) => forecastData.HighC = v.ConvertToShort());
                    nodeNavigator.ReadSingleNode("low/fahrenheit", (v) => forecastData.LowF = v.ConvertToShort());
                    nodeNavigator.ReadSingleNode("low/celsius", (v) => forecastData.LowC = v.ConvertToShort());

                    nodeNavigator.ReadSingleNode("conditions", (v) => forecastData.Conditions = v);
                    nodeNavigator.ReadSingleNode("icon", (v) => forecastData.Icon = v);
                    nodeNavigator.ReadSingleNode("skyicon", (v) => forecastData.SkyIcon = v);

                    nodeNavigator.ReadSingleNode("pop", (v) => forecastData.Pop = v.ConvertToShort());

                    forecastDatas.Add(forecastData);
                }
            }

            result.Forecasts = forecastDatas.AsReadOnly();
        }
    }
}
